/**
 * \file PageTable.cc
 * \brief x86 page tables
 * \author Corey Tabaka
 */

/*
   Copyright 2006 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <platform/PageTable.h>
#include <platform/RegUtil.h>
#include <platform/Memory.h>
#include <vm/Page.h>
#include <Queue.h>
#include <Log.h>
#include <malloc.h>
#include <i386/mem.h>

typedef struct {
	Queue list;
	uint n;
	int *refs;
	uint8 *block;
} table_arena_t;

static Queue arenas;

void PageTable::initialize(void) {
	
}

PageTable::PageTable(void) : lock() {
	pageDir = (page_entry_t *) memalign(PAGE_ALIGN, PAGE_SIZE);
	memset(pageDir, 0, PAGE_SIZE);
}

PageTable::~PageTable(void) {
	// TODO: delete page tables based on the shared flag
	free(pageDir);
}

void *PageTable::acquire(void *pa, void *ea, uint32 len, bool user,
	uint32 mode) {
	//Log::printf(LOG_LOW, "memory_acquire: pa=%08x len=%d\n", (uint32) pa, len);
	
	lock.lock();
	
	page_entry_t *table = 0;
	
	uint32 pbase = ((uint32) pa & 0xfffff000);
	uint32 ebase = ((uint32) ea & 0xfffff000);
	uint32 limit = PAGE_ROUND(len);
	
	//printf("    base=%08x limit=%08x len=%08x\n", ebase, limit, len);
	
	uint32 start = (ebase & 0xfffff000) >> 12;
	uint32 pages = (limit & 0xfffff000) >> 12;
	
	//printf("start=%d pages=%d\n", start, pages);
	
	for (uint32 p=start, i=pbase; p < start + pages;) {
		uint32 t = (p >> 10);
		
		//printf("dir offset %d, pbase=%08x\n", t, i);
		
		if (!pageDir[t].present) {
			//printf("new page for not present at dir offset %d\n", t);
			// allocate a new page table
			pageDir[t].page = memalign(PAGE_ALIGN, PAGE_SIZE);
			memset(pageDir[t].page, 0, sizeof(page_table_t));
			table = (page_entry_t *) pageDir[t].page;
			
			pageDir[t].present = 1;
			pageDir[t].rw = 1;
			pageDir[t].user_priv = 1; // we don't use table level control
			
			//invalidatePage((void *) (p & 0xfffff000));
		} else if (mode & ACQUIRE_MODE_NEW_PDE) {
			//printf("new page for present at dir offset %d\n", t);
			// allocate a new page table
			pageDir[t].page = memalign(PAGE_ALIGN, PAGE_SIZE);
			memset(pageDir[t].page, 0, sizeof(page_table_t));
			table = (page_entry_t *) pageDir[t].page;
			
			pageDir[t].present = 1;
			pageDir[t].rw = 1;
			pageDir[t].user_priv = 1; // we don't use table level control
			
			//invalidatePage((void *) (p & 0xfffff000));
		} else if (mode & ACQUIRE_MODE_COPY_PDE) {
			//printf("copy page at dir offset %d\n", t);
			void *origPage = (void *) ((uint32) pageDir[t].page & 0xfffff000);
			invalidatePage((void *) p);
			
			// allocate a new page table
			pageDir[t].page = memalign(PAGE_ALIGN, PAGE_SIZE);
			memcpy(pageDir[t].page, origPage, sizeof(page_table_t));
			table = (page_entry_t *) pageDir[t].page;
			
			pageDir[t].present = 1;
			pageDir[t].rw = 1;
			pageDir[t].user_priv = 1; // we don't use table level control
			
			//invalidatePage((void *) (p & 0xfffff000));
		} else {
			//printf("existing page at dir offset %d\n", t);
			table =
				(page_entry_t *) ((uint32) pageDir[t].page & 0xfffff000);
		}
		
		for (uint32 e=p & 0x3ff; e < 0x400 && p < start + pages;
			e++, p++, i+=PAGE_SIZE) {
			//printf("table offset % 4d, pbase=%08x\n", e, i);
			
			if (!table[e].present || mode & ACQUIRE_MODE_REPLACE_PTE) {
				//printf("table entry % 4d set to %08x\n", e, i);
				
				bool invalidate = true; //table[e].present;
				
				table[e].page = (void *) i;
				table[e].present = 1;
				table[e].rw = mode & ACQUIRE_MODE_READ_ONLY ? 0 : 1;
				table[e].user_priv = user ? 1 : 0;
				table[e].cache_disable =
					mode & ACQUIRE_MODE_DISABLE_CACHE ? 1 : 0;
				
				if (invalidate) {
					invalidatePage((void *) (p & 0xfffff000));
				}
			}
		}
	}
	
	lock.unlock();
	
	return (void *) ebase;
}

void PageTable::release(void *va, uint32 len) {
	lock.lock();
	
	page_entry_t *table = 0;
	
	uint32 ebase = ((uint32) va & 0xfffff000);
	uint32 limit = PAGE_ROUND(len);
	
	uint32 start = (ebase & 0xfffff000) >> 12;
	uint32 pages = (limit & 0xfffff000) >> 12;
	
	for (uint32 p=start; p < start + pages;) {
		uint32 t = (p >> 10);
		
		if (pageDir[t].present) {
			table = (page_entry_t *) ((uint32) pageDir[t].page & 0xfffff000);
			
			for (uint32 e=p & 0x3ff; e < 0x400 && p < start + pages; e++, p++) {
				if (table[e].present) {
					table[e].present = 0;
					invalidatePage((void *) (p & 0xfffff000));
				}
			}
		}
	}
	
	lock.unlock();
}

void *PageTable::physicalFromEffective(void *ea, bool *present) {
	lock.lock();
	
	uint32 d = (uint32) ea >> 22;
	uint32 t = ((uint32) ea & 0x003ff000) >> 12;
	uint32 o = ((uint32) ea & 0x00000fff);
	
	if (pageDir[d].present) {
		page_entry_t *table =
			(page_entry_t *) ((uint32) pageDir[d].page & 0xfffff000);
		
		if (table[t].present) {
			if (present) {
				*present = true;
			}
			
			lock.unlock();
			return (void *) ((uint32) table[t].page & 0xfffff000 | o);
		}
	}
	
	if (present) {
		*present = false;
	}
	
	lock.unlock();
	return 0;
}

void PageTable::copy(const PageTable *pd) {
	lock.lock();
	((PageTable *) pd)->lock.lock();
	
	memcpy(pageDir, pd->pageDir, PAGE_SIZE);
	
	((PageTable *) pd)->lock.unlock();
	lock.unlock();
}

bool PageTable::getFlags(uint32 va, uint32 &flags) {
	lock.lock();
	
	uint32 d = va >> 22;
	uint32 t = (va & 0x003ff000) >> 12;
	
	bool ret;
	if (pageDir[d].present) {
		page_entry_t *table =
			(page_entry_t *) ((uint32) pageDir[d].page & 0xfffff000);
		
		if (table[t].present) {
			flags = (uint32) table[t].page & PAGE_MASK;
			ret = true;
		} else {
			ret = false;
		}
	} else {
		ret = false;
	}
	
	lock.unlock();
	return ret;
}

bool PageTable::updateFlags(uint32 va, uint32 flags, uint32 mask) {
	lock.lock();
	
	uint32 d = va >> 22;
	uint32 t = (va & 0x003ff000) >> 12;
	
	bool ret;
	if (pageDir[d].present) {
		page_entry_t *table =
			(page_entry_t *) ((uint32) pageDir[d].page & 0xfffff000);
		
		if (table[t].present) {
			uint32 *entry = (uint32 *) &table[t].page;
			
			*entry &= ~mask | ~PAGE_MASK;
			*entry |= flags & PAGE_MASK;
			ret = true;
		} else {
			ret = false;
		}
	} else {
		ret = false;
	}
	
	lock.unlock();
	return ret;
}

bool PageTable::isCurrent(void) {
	return pageDir == getCR3();
}

void PageTable::makeCurrent(void) {
	if (!isCurrent()) {
		loadCR3(pageDir);
	}
}
